Skip to content

fix(otlp): accept flexible timestamp formats in JSON payloads#1108

Merged
jchrostek-dd merged 5 commits intomainfrom
john/otlp-issue
Mar 17, 2026
Merged

fix(otlp): accept flexible timestamp formats in JSON payloads#1108
jchrostek-dd merged 5 commits intomainfrom
john/otlp-issue

Conversation

@jchrostek-dd
Copy link
Copy Markdown
Contributor

@jchrostek-dd jchrostek-dd commented Mar 17, 2026

Summary

  • Adds normalization layer to convert OTLP JSON timestamps to string format before deserialization
  • Supports string timestamps (proto3 JSON spec), integer timestamps (common in some SDKs), and object timestamps {"low": n, "high": m} (from buggy older JS SDKs)
  • Fixes errors like "invalid type: integer, expected a string" seen with serverless-self-monitoring

Root Cause

The opentelemetry-proto crate's serde deserializer only accepts string-encoded 64-bit timestamps (per proto3 JSON spec), but some OpenTelemetry SDKs send timestamps as integers or objects.

serverless-self-monitoring uses outdated SDK versions:

Handler Package Version
node18/20/22 @opentelemetry/exporter-trace-otlp-http 0.36.1 (~Feb 2023)
python310 opentelemetry-exporter-otlp-proto-http 1.15.0 (~Feb 2023)

The extension's integration tests use 0.54.2 (current), which properly serializes timestamps as strings, so this issue wasn't caught.

Known upstream issue: opentelemetry-rust #1662 - The Rust opentelemetry-proto crate's serde implementation doesn't accept both string and integer formats for 64-bit integers, as the proto3 JSON spec recommends.

Related JS SDK issue: opentelemetry-js #4216 - Older JS SDK versions sent timestamps as {"low": n, "high": m} objects instead of strings.

Solution

Instead of waiting for upstream fixes or requiring all senders to update their SDKs, we normalize JSON timestamps before deserialization:

  1. Parse JSON into serde_json::Value
  2. Recursively find timestamp fields (startTimeUnixNano, endTimeUnixNano, timeUnixNano, observedTimeUnixNano)
  3. Convert integers → strings, objects {"low": n, "high": m} → reconstructed string
  4. Deserialize normalized JSON with opentelemetry-proto

Test plan

  • Unit tests for timestamp normalization (string, integer, object formats)
  • Unit tests for nested structure normalization
  • Full test suite passes (506 tests)
  • Clippy passes with -D warnings
  • Integration tests with serverless-self-monitoring

🤖 Generated with Claude Code

jchrostek-dd and others added 2 commits March 17, 2026 09:50
The opentelemetry-proto crate's serde deserializer only accepts
string-encoded 64-bit timestamps (per proto3 JSON spec), but some
OpenTelemetry SDKs send timestamps as integers or objects.

This change adds a normalization layer that converts timestamps to
strings before deserialization, supporting:
- Strings (proto3 JSON spec compliant) - unchanged
- Integers (common in some SDKs) - converted to strings
- Objects {"low": n, "high": m} (buggy older JS SDKs) - reconstructed and converted

Fixes errors like:
- "invalid type: integer `[timestamp]`, expected a string"
- DecodeError when JSON payloads parsed as protobuf

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@duncanista
Copy link
Copy Markdown
Contributor

@jchrostek-dd
Copy link
Copy Markdown
Contributor Author

We already have with-serde enabled (see Cargo.toml line 53).

The issue is that PR #1666's deserialize_string_to_u64 function only accepts strings:

pub fn deserialize_string_to_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
    D: Deserializer<'de>,
{
    let s: String = Deserialize::deserialize(deserializer)?;  // ← Only accepts String
    s.parse::<u64>().map_err(de::Error::custom)
}

When the JSON contains an integer timestamp like "startTimeUnixNano": 1773408852484000000 (without quotes), this fails with invalid type: integer, expected a string because it's trying to deserialize a JSON number as a Rust String.

The proto3 JSON spec says 64-bit integers SHOULD be string-encoded, and that's what PR #1666 implemented. However, some OpenTelemetry SDKs (particularly older JS versions like 0.36.1) don't follow the spec and send integers. There's an open issue about this.

Our normalization layer converts these integer timestamps to strings before passing to serde, making us compatible with both spec-compliant and non-compliant SDKs.

@jchrostek-dd jchrostek-dd marked this pull request as ready for review March 17, 2026 14:40
@jchrostek-dd jchrostek-dd requested a review from a team as a code owner March 17, 2026 14:40
@jchrostek-dd jchrostek-dd requested review from Copilot and lym953 March 17, 2026 14:40
Copy link
Copy Markdown
Contributor

@litianningdatadog litianningdatadog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@gh-worker-ownership-write-b05516 gh-worker-ownership-write-b05516 bot review requested due to automatic review settings March 17, 2026 14:43
@jchrostek-dd jchrostek-dd merged commit 7225b58 into main Mar 17, 2026
50 checks passed
@jchrostek-dd jchrostek-dd deleted the john/otlp-issue branch March 17, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants